package com.yifeng.repo.base.utils.async;

import com.yifeng.repo.base.utils.common.BaseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 定时线程执行器
 * Created by daibing on 2020/3/8.
 */
public class FireScheduledExecutor {
    private static final Logger LOGGER = LoggerFactory.getLogger(FireScheduledExecutor.class);
    private static final Integer STAT_THRESHOLD = 100;
    private final AtomicInteger count = new AtomicInteger(0);
    private final ScheduledThreadPoolExecutor executor;
    private final Integer corePoolSize;
    private final String threadNamePrefix;

    public FireScheduledExecutor(Integer corePoolSize, String threadNamePrefix) {
        this.corePoolSize = corePoolSize == null ? 1 : corePoolSize;
        this.threadNamePrefix = BaseUtil.isBlank(threadNamePrefix) ? "fire-timer" : threadNamePrefix;
        this.executor = this.init();
    }

    private ScheduledThreadPoolExecutor init() {
        return new ScheduledThreadPoolExecutor(
                corePoolSize,
                new ThreadFactory() {
                    final Random random = new Random();
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName(threadNamePrefix + this.random.nextInt());
                        return thread;
                    }
                },
                new ThreadPoolExecutor.CallerRunsPolicy()) {
            @Override
            protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
                return new RunnableScheduledFuture<V>() {
                    @Override
                    public boolean isPeriodic() {
                        return task.isPeriodic();
                    }
                    @Override
                    public long getDelay(TimeUnit unit) {
                        return task.getDelay(unit);
                    }
                    @Override
                    public int compareTo(Delayed o) {
                        return task.compareTo(o);
                    }
                    @Override
                    public void run() {
                        countNumber();
                        task.run();
                    }
                    @Override
                    public boolean cancel(boolean mayInterruptIfRunning) {
                        return task.cancel(mayInterruptIfRunning);
                    }
                    @Override
                    public boolean isCancelled() {
                        return task.isCancelled();
                    }
                    @Override
                    public boolean isDone() {
                        return task.isDone();
                    }
                    @Override
                    public V get() throws InterruptedException, ExecutionException {
                        return task.get();
                    }
                    @Override
                    public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
                        return task.get(timeout, unit);
                    }
                };
            }
        };
    }

    public void shutdown() {
        this.executor.shutdown();
    }

    public void scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        this.executor.scheduleWithFixedDelay(command, initialDelay, delay, unit);
    }

    public void scheduleAtFixedRate(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        this.executor.scheduleAtFixedRate(command, initialDelay, delay, unit);
    }

    public void printPoolStatus() {
        this.printPoolStatus(count.get());
    }

    private void printPoolStatus(int count) {
        LOGGER.info("ThreadName={}, Count={}, PoolSize={}, LargestPoolSize={}, TaskCount={}, CompletedTaskCount={}, ActiveCount={}, timerQueueSize={}",
                threadNamePrefix,
                count,
                executor.getPoolSize(),
                executor.getLargestPoolSize(),
                executor.getTaskCount(),
                executor.getCompletedTaskCount(),
                executor.getActiveCount(),
                executor.getQueue().size()
        );
        // temp test ...
        // System.out.println("countNumber2: " + count);
    }

    private void countNumber() {
        if (STAT_THRESHOLD.compareTo(count.incrementAndGet()) <= 0) {
            this.printPoolStatus(count.getAndSet(0));
        }
        // temp test ...
        // System.out.println("countNumber1: " + count.get());
    }

    public static void main(String[] args) throws Exception {
        System.out.println("main start to do...");
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        AtomicInteger number = new AtomicInteger(0);

        FireScheduledExecutor executor = new FireScheduledExecutor(1, "my-timer");
        Runnable command = new Runnable() {
            @Override
            public void run() {
                System.out.printf("runnable time=%s, number=%s%n", sdf.format(new Date()), number.incrementAndGet());
            }
        };
        executor.scheduleAtFixedRate(command, 1000, 100, TimeUnit.MILLISECONDS);
        System.out.println("start to schedule run...");
        System.in.read();
    }

}
